原型链

# 原型链

常见原型链图

参考链接:https://www.cnblogs.com/xiaohuochai/p/5721552.html (opens new window)

当谈到继承时,JavaScript 只有一种结构:对象。 首先,每个js对象都会对应一个原型对象,并从原型对象继承属性和方法。 其次,_proto_(隐式原型)的值就是它对应的原型对象。 然后,constructor的值就是原型对象对应的构造函数。

**每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype )。**该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null

根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

[TOC]

# 一、一张图的事

原型链

function Foo() {

}

Foo.prototype.getName = function () {
    console.log(3);
};


var foo = new Foo();
foo.getName();//3
Foo.prototype.getName();//3
Foo.getName();//TypeError:Foo.getName is not a function

// 因为Javascript查找属性或方法的顺序是先查找对象本身然后查找对象的原型链
// 不会针对函数而查找函数的prototype属性的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 1.1 Foo.getName()找法

1.找本身 没有getName方法 2.找__proto__,发现指向Function.prototype。 3.在Function.prototype上找,没找到 4.找在Function.prototype.__proto__,发现指向Object.prototype 5.在Object.prototype上找没找到,去Object.prototype.__proto__找 6.发现没有Object.prototype.__proto__ 7.结束

# 1.2 foo.getName()找法

1.找本身 没有getName方法 2.找__proto__,发现指向Foo.prototype 3.在Foo.prototype上发现getName方法 4.执行

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

const member = new Person("Lydia", "Hallie");
Person.getFullName = () => this.firstName + this.lastName;

console.log(member.getFullName());	// TypeError:member.getFullName is not a function
1
2
3
4
5
6
7
8
9

不能像使用常规对象那样向构造函数添加属性。 如果要一次向所有对象添加功能,则必须使用原型。 所以在这种情况下应该这样写:

Person.prototype.getFullName = function () {
  return `${this.firstName} ${this.lastName}`;
}
1
2
3

这样会使member.getFullName()是可用的,为什么样做是对的? 假设我们将此方法添加到构造函数本身。 也许不是每个Person实例都需要这种方法。这会浪费大量内存空间,因为它们仍然具有该属性,这占用了每个实例的内存空间。 相反,如果我们只将它添加到原型中,我们只需将它放在内存中的一个位置,但它们都可以访问它!

# 二、prototype(显式原型)

  • 不像每个对象都有__proto__属性来标识自己所继承的原型,只有函数才有prototype属性。
  • 每一个函数在创建之后都会拥有一个名为 prototype 的属性,这个属性指向函数的原型对象。( 通过 Function.prototype.bind 方法构造出来的函数是个例外,它没有 prototype 属性 )。

函数在JS中真的很特殊,是所谓的_一等公民_。JS不像其它面向对象的语言,它没有类(class,ES6引进了这个关键字,但更多是语法糖)的概念。JS通过函数来模拟类。

当你创建函数时,JS会为这个函数自动添加prototype属性,值是空对象。而一旦你把这个函数当作构造函数(constructor)调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__proto__指向承构造函数的prototype来实现这种继承)。

实例对象Object和Foo本身没有constructor属性,需要继承原型对象Function.prototype的constructor属性。

作用

  1. 显式原型:用来实现基于原型的继承与属性的共享。
  2. 隐式原型:构成原型链,同样用于实现基于原型的继承。

# 三、new基本原理

// 模拟new原理
// func为构造函数
function new1(func, ...args) {
    // 1.创建空对象
    let obj = {};
    // 2.将原型指向构造函数的原型
    obj.__proto__ = func.prototype;
    // 3.将this指向新实例,并执行构造函数的代码
    let res = func.call(obj, ...args);
    // 4.如果构造函数返回的是对象((包含 Functoin,Array,Date,RegExg,Error),则直接返回;否则返回创建的新对象
    return (typeof res === 'object'|| typeof res === 'function'&& res ? res : obj;
            }

// 检验
function Test(name, age) {
    this.name = name;
    this.age = age;
}

let test = new1(Test, 'Lin', 22);
console.log(test); // {name:'Lin',age:'22'}
console.log(test instanceof Test); // true
console.log(test instanceof Object); // true
console.log(test.__proto__.constructor === Test); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

PS:在不用传参的情况下,new test = new Test()。

# 四、原型、构造函数、实例及原型链

img

任何一个函数,如果在前面加了new,那就是构造函数。

  • 已知A继承了B,B继承了C。怎么判断 a 是由A直接生成的实例,还是B直接生成的实例呢?还是C直接生成的实例呢?
    • 用原型的constructor属性
    • foo.__proto__.constructor === Foo的结果为true,但是 foo.__proto__.constructor === Object的结果为false。
    • 而用instanceof则不够严谨。
    • foo instanceof Foo的结果为true,因为foo.__proto__ === Foo.prototype为true。
    • foo instance of Objecet的结果也为true,因为Foo.prototype.__proto__ === Object.prototype为true。

# 五、原型的实际应用

# 5.1 zepto如何使用原型

(function (window) {

    var zepto = {}

    function Z(dom, selector) {
        var i, len = dom ? dom.length : 0
        for (i = 0; i < len; i++) {
            this[i] = dom[i]
        }
        this.length = len
        this.selector = selector || ''
    }

    zepto.Z = function (dom, selector) {
        return new Z(dom, selector)
    }

    zepto.init = function (selector) {
        var slice = Array.prototype.slice
        var dom = slice.call(document.querySelectorAll(selector))
        return zepto.Z(dom, selector)
    }

    var $ = function (selector) {
        return zepto.init(selector)
    }
    window.$ = $

    $.fn = {
        css: function (key, value) {
            alert('css')
        },
        html: function (value) {
            return '这是一个模拟的 html 函数'
        }
    }
    Z.prototype = $.fn
})(window)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

把原型方法放在$.fn,可以方便扩展插件

$.fn.getNodeName = function() {
    return this[0].nodeName;
}
1
2
3

只有$会暴露在window全局变量。

将插件扩展统一到$.fn.XXX这一接口,方便使用。

# 5.2 jQuery如何使用原型

(function (window) {

    var jQuery = function (selector) {
        return new jQuery.fn.init(selector)
    }

    jQuery.fn = {
        css: function (key, value) {
            alert('css')
        },
        html: function (value) {
            return 'html'
        }
    }

    var init = jQuery.fn.init = function (selector) {
        var slice = Array.prototype.slice
        var dom = slice.call(document.querySelectorAll(selector))

        var i, len = dom ? dom.length : 0
        for (i = 0; i < len; i++) {
            this[i] = dom[i]
        }
        this.length = len
        this.selector = selector || ''
    }

    init.prototype = jQuery.fn

    window.$ = jQuery

})(window)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 5.3 插件机制